/*
* Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved.
* Description: ff demuser
* Author: HiMobileCam middleware develop team
* Create: 2017-12-22
*/

#include "hi_ffmpeg_format.h"

#ifndef __HuaweiLite__
#include <sys/syscall.h>
#endif
#include "securec.h"

#include "hi_demuxer_err.h"
#include "ffdemux_log.h"
#include "ffdemux_common.h"
#include "common_format.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* __cplusplus */

static HI_S32 ReadFfDemuxFormatMemberAttr(FfDemuxFormatMember *formatMember, const HI_CHAR fileName[])
{
    if (realpath(fileName, formatMember->filePath) == HI_NULL) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
            "realpath %s ret failed errno: %d!\n", fileName, errno);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    AVFormatContext *fmtCtx = HI_NULL;
    /* open input file, and allocate format context */
    HI_S32 ret = avformat_open_input(&fmtCtx, formatMember->filePath, HI_NULL, HI_NULL);
    if (ret < 0) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "Could not open source file %s, ret:%d \n",
                       formatMember->filePath, ret);
        return HI_ERR_DEMUXER_OPEN_FILE;
    }
    formatMember->formatContext = fmtCtx;

    if (strstr(fmtCtx->iformat->name, "mpegts")) {
        HI_U32 flag = (HI_U32)fmtCtx->flags;
        flag = (flag | AVFMT_FLAG_KEEP_SIDE_DATA);
        fmtCtx->flags = (HI_S32)flag;
        formatMember->enFormat = HI_FFDEMUX_FORMAT_TS;
    } else if (strstr(fmtCtx->iformat->name, "mov") || strstr(fmtCtx->iformat->name, "mp4") ||
               strstr(fmtCtx->iformat->name, "m4a")) {
        formatMember->enFormat = HI_FFDEMUX_FORMAT_MP4;
    } else if (strstr(fmtCtx->iformat->name, "wav")) {
        formatMember->enFormat = HI_FFDEMUX_FORMAT_WAV;
    } else {
        avformat_close_input(&formatMember->formatContext);
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
            "invalid  file format %s\n", fmtCtx->iformat->name);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    if (fmtCtx->nb_streams > HI_DEMUXER_RESOLUTION_CNT) {
        avformat_close_input(&formatMember->formatContext);
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
            "stream num:%d reach max  %d\n", fmtCtx->nb_streams, HI_DEMUXER_RESOLUTION_CNT);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    return HI_SUCCESS;
}
static HI_VOID FfAvbsfDeinit(AVBSFContext **bsfCtx)
{
    av_bsf_free(bsfCtx);
    return;
}

static HI_VOID FfDicardStream(FfDemuxFormatMember *formatMember)
{
    HI_S32 streamNum = formatMember->formatContext->nb_streams;
    for (HI_S32 i = 0; i < streamNum; i++) {
        if (i != formatMember->usedVideoStreamIndex && i != formatMember->audioStreamIndex) {
            formatMember->formatContext->streams[i]->discard = AVDISCARD_ALL;
        } else {
            formatMember->formatContext->streams[i]->discard = AVDISCARD_NONE;
        }
    }
    return;
}

HI_S32 FfDemuxOpen(HI_VOID **outFormat, const HI_CHAR fileName[])
{
    if ((outFormat == HI_NULL) || (fileName == HI_NULL)) {
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    HI_S32 fileLen = strlen(fileName);
    if (fileLen < 1 || fileLen > PATH_MAX) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
            "ffmpeg demux  file len is %d beyond [1,%d] \n", fileLen, PATH_MAX);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)malloc(sizeof(FfDemuxFormatMember));
    if (formatMember == HI_NULL) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "malloc FfDemuxFormatMember failed \n");
        return HI_ERR_DEMUXER_NULL_PTR;
    }
    if (memset_s(formatMember, sizeof(FfDemuxFormatMember), 0x00,
                 sizeof(FfDemuxFormatMember)) != EOK) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "SET FfDemuxFormatMember failed \n");
        free(formatMember);
        return HI_FAILURE;
    }

    formatMember->audioStreamIndex = HI_DEMUXER_NO_MEDIA_STREAM;
    formatMember->videoStreamIndex = HI_DEMUXER_NO_MEDIA_STREAM;
    formatMember->metaStreamIndex = HI_DEMUXER_NO_MEDIA_STREAM;
    for (HI_S32 i = 0; i < HI_DEMUXER_RESOLUTION_CNT; i++) {
        formatMember->stSteamResolution[i].enVideoType = HI_FORMAT_VIDEO_TYPE_BUTT;
        formatMember->stSteamResolution[i].s32VideoStreamIndex = HI_DEMUXER_NO_MEDIA_STREAM;
        formatMember->stSteamResolution[i].u32Height = 0;
        formatMember->stSteamResolution[i].u32Width = 0;
    }

    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    av_init_packet(&formatMember->pkt);

    HI_S32 ret = ReadFfDemuxFormatMemberAttr(formatMember, fileName);
    if (ret != HI_SUCCESS) {
        free(formatMember);
        return ret;
    }

    pthread_mutex_init(&formatMember->demuxMutex, HI_NULL);
    *outFormat = (HI_VOID *)formatMember;
    FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_INFO, "input format: %s\n",
        formatMember->formatContext->iformat->name);
    return HI_SUCCESS;
}

HI_S32 FfDemuxClose(HI_VOID *format)
{
    LOG_AND_RETURN_IF_NULL(format);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;

    FFDEMUX_LOCK(formatMember->demuxMutex);
    avformat_close_input(&formatMember->formatContext);

    if (formatMember->h26XBsfCtx) {
        FfAvbsfDeinit(&formatMember->h26XBsfCtx);
        formatMember->h26XBsfCtx = HI_NULL;
    }

    FFDEMUX_UNLOCK(formatMember->demuxMutex);
    pthread_mutex_destroy(&(formatMember->demuxMutex));

    free(formatMember);
    formatMember = HI_NULL;
    return HI_SUCCESS;
}

HI_S32 FfDemuxRead(HI_VOID *format, HI_FORMAT_PACKET_S *fmtFrame)
{
    LOG_AND_RETURN_IF_NULL(format);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;

    if (fmtFrame == HI_NULL) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "fmtFrame is null \n");
        return HI_ERR_DEMUXER_NULL_PTR;
    }

    /* initialize packet, set data to HI_NULL, let the demuxer fill it */
    FFDEMUX_LOCK(formatMember->demuxMutex);

    HI_FORMAT_PACKET_S frame = { 0 };
    HI_S32 ret = DemuxerRead(formatMember, &frame);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_INFO, "read packet fail:%d  \n", ret);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }

    if (memcpy_s(fmtFrame, sizeof(HI_FORMAT_PACKET_S), &frame, sizeof(HI_FORMAT_PACKET_S)) != EOK) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "COPY fmtFrame FAIL  \n");
    }

    if (fmtFrame->s64Pts < 0) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_INFO, "demux out pts:%lld negative err \n",
                       fmtFrame->s64Pts);
        fmtFrame->s64Pts = 0;
    }

    FFDEMUX_UNLOCK(formatMember->demuxMutex);

    return HI_SUCCESS;
}

HI_S32 FfDemuxFree(HI_VOID *format, const HI_FORMAT_PACKET_S *fmtFrame)
{
    LOG_AND_RETURN_IF_NULL(format);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;

    if (fmtFrame == HI_NULL) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "fmtFrame is null \n");
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    FFDEMUX_LOCK(formatMember->demuxMutex);

    HI_S32 ret = DemuxerFree(formatMember, fmtFrame);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "free packet fail:%d  \n", ret);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }

    FFDEMUX_UNLOCK(formatMember->demuxMutex);
    return HI_SUCCESS;
}

HI_S32 FfDemuxSeek(HI_VOID *format, HI_S32 streamIndex, HI_S64 toMs, HI_S32 flag)
{
    LOG_AND_RETURN_IF_NULL(format);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;

    if (streamIndex == HI_DEMUXER_NO_MEDIA_STREAM) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "streamIndex  is  %d \n", streamIndex);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    HI_S64 timeUs = ((HI_S64)toMs * FFMPEG_MS_US_UNIT);
    /* timeUs may oversize s64 to be negative value */
    if (toMs < 0 || timeUs < 0) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
            "seek input time is negative or oversize: %lld ms\n", toMs);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    if (timeUs > formatMember->formatContext->duration) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR,
                       "seek input time is bigger total time seektime: %lld\n",
                       timeUs);
        return HI_ERR_DEMUXER_ILLEGAL_PARAM;
    }

    FFDEMUX_LOCK(formatMember->demuxMutex);
    HI_S32 ret = DemuxerSeek(formatMember, streamIndex, toMs, flag);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_WARN,
            "seek fail:%x,max len=%lld,seek to : %lld \n", ret,
            formatMember->formatContext->duration / 1000000LL, toMs / 1000LL);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }

    FFDEMUX_UNLOCK(formatMember->demuxMutex);
    return HI_SUCCESS;
}

HI_S32 FfDemuxProbe(HI_VOID *format)
{
    LOG_AND_RETURN_IF_NULL(format);

    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;
    FFDEMUX_LOCK(formatMember->demuxMutex);

    HI_S32 ret = DemuxerProbe(formatMember);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "probe fail:%d  \n", ret);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }
    FfDicardStream(formatMember);

    FFDEMUX_UNLOCK(formatMember->demuxMutex);
    return HI_SUCCESS;
}

HI_S32 FfDemuxGetinfo(HI_VOID *format, HI_FORMAT_FILE_INFO_S *fmtInfo)
{
    LOG_AND_RETURN_IF_NULL(format);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;

    if (fmtInfo == HI_NULL) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "fmtInfo is null \n");
        return HI_ERR_DEMUXER_NULL_PTR;
    }

    FFDEMUX_LOCK(formatMember->demuxMutex);

    HI_FORMAT_FILE_INFO_S fileInfo = { 0 };
    HI_S32 ret = DemuxerGetinfo(formatMember, &fileInfo);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "get fileinfo fail:%d  \n", ret);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }

    if (memcpy_s(fmtInfo, sizeof(HI_FORMAT_FILE_INFO_S), &fileInfo,
                 sizeof(HI_FORMAT_FILE_INFO_S)) != EOK) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "COPY fmtInfo FAIL  \n");
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return HI_FAILURE;
    }

    FFDEMUX_UNLOCK(formatMember->demuxMutex);
    return HI_SUCCESS;
}

HI_S32 FfDemuxSetattr(UNUSED_ATTR HI_VOID *format, HI_FORMAT_MEDIA_ATTR_S *fmtInfo)
{
    LOG_AND_RETURN_IF_NULL(format);
    LOG_AND_RETURN_IF_NULL(fmtInfo);
    FfDemuxFormatMember *formatMember = (FfDemuxFormatMember *)format;
    FFDEMUX_LOCK(formatMember->demuxMutex);

    HI_S32 ret = DemuxerSetattr(formatMember, fmtInfo);
    if (ret != HI_SUCCESS) {
        FFDEMUX_PRINTF(MODULE_NAME_DEMUX, HI_FFDEMUX_LOG_LEVEL_ERR, "set attr fail:%d  \n", ret);
        FFDEMUX_UNLOCK(formatMember->demuxMutex);
        return ret;
    }
    FfDicardStream(formatMember);

    FFDEMUX_UNLOCK(formatMember->demuxMutex);

    return HI_SUCCESS;
}

#ifndef __HI3518EV300__
/* ffmpeg demuxer symbol */
HI_DEMUX_S g_stFormatEntry = { "ffmpeg_demuxer", "mp4,lrv,mov,mpegts,m4a,wav",
    {
        FfDemuxOpen, FfDemuxClose, FfDemuxRead, FfDemuxFree, FfDemuxSeek,
        FfDemuxProbe, FfDemuxGetinfo, FfDemuxSetattr
    },
    2
};
#else
HI_DEMUX_S g_stFormatEntry = { "ffmpeg_demuxer", "mp4,lrv,mov,mpegts,m4a",
    {
        FfDemuxOpen, FfDemuxClose, FfDemuxRead, FfDemuxFree, FfDemuxSeek,
        FfDemuxProbe, FfDemuxGetinfo, FfDemuxSetattr
    },
    2
};
#endif

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
